﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

/**
 * \file DestructableObject.cs
 */

/**
 * DestructableObjects can be destroyed.
 * DestructableObjects can be told to
 * stay in memory after they have left the screen
 * in order for them to continue updating and
 * destroy themselves if necessary.
 */
public class DestructableObject : ViewableObject
{

    public readonly Vector3 maxDnSpeed = new Vector3(0, 20, 0);
    protected Vector3 jumpSpeed = new Vector3(0, -7, 0);
    protected Vector3 gravity = new Vector3(0, .2f, 0);
    protected Vector3 currVelocity = new Vector3(0, 0, 0);
    protected static Texture2D shadow = Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.ENVIRONMENT).get("shadow").spriteSheet;
    protected Rectangle shadowDest = new Rectangle();
    protected bool showShadow = false;
    protected bool squashed = false;
    protected bool burned = false;
    protected bool melted = false;
    protected bool electrocuted = false;
    protected bool destroyed = false;
    protected int health = 1;
    public int getHealth
    {
        get { return health; }
    }

    /**
     * true if this object should stay in 
     * memory after leaving the screen.
     */
    protected bool stayActive = false;

    /**
     * the Animation of the destruction for
     * this object.
     */
    protected Animation destruction;

    /**
     * this object's type
     */
    protected new DestructableObject.Type thisType;

    protected bool physics = true;

    public void setVelocity(Vector3 delta)
    {
        currVelocity = delta;
    }

    /**
     * 
     */
    public DestructableObject(DestructableObject.Type type, Vector3 center)
        : base(type, center)
    {
        init(type, center);
    }

    private void init(DestructableObject.Type type, Vector3 center)
    {
        thisType = type;
        destruction = new Animation(thisType.explodeName, thisType.explodeFramerate, thisType.explodeLoopable, thisType.explodeFrames);
    }

    /**
     * used to tell this instance to stay in memory when offscreen.
     * @param stay true means this object stays in memory if offscreen,
     * false means this object will be culled if offscreen.
     */
    public void stayInMemory(Boolean stay)
    {
        stayActive = stay;
    }

    /**
     * tells this instance to go into its destruction
     * Animation and clear itself.
     */
    public virtual void destroy(CollidableObject co)
    {
        if (!destroyed)
        {
            destroyed = true;
            currentAnimation = destruction;
            //physics = false;
            //currVelocity = new Vector3(0, 0, 0);
        }
    }

    public override void update(double elapsed)
    {
        CollidableObject destructor = checkHazards();
        if (checkSquashed())
        {
            if (health > 0 && myType.myType == ObjectFactory.TypeEnum.QuinHero)
            {
                --health;
                Game.getGame().getSoundManager().playOneSoundEffect(((AudioAsset)Game.getGame().getAssetLibrary().getAudio(AssetLibrary.audioType.SOUND_EFFECT).get("death_squish")).sound, 1.0f);
            }
            destroy(this);
        }
        if (destructor != null)
        {
            destroy(destructor);
        }
        if (!stayActive)
        {
            base.update(elapsed);
        }
        else
        {
            currentAnimation.update(elapsed);
            screenCoord = Camera.worldToScreen(origin);
        }
        if (currentAnimation == destruction && currentAnimation.isFinished)
        {
            cull = true;
        }
        if (physics)
        {
            updateFalling();
        }
        if (showShadow)
        {
            updateShadow();
        }
    }

    protected CollidableObject checkHazards()
    {
        List<CollidableObject> hazardCheck = LevelBase.collidesWith(this);
        foreach (CollidableObject co in hazardCheck)
        {
            if (co is FireObject)
            {
                if (health > 0 && myType.myType == ObjectFactory.TypeEnum.QuinHero)
                {
                    --health;
                    Game.getGame().getSoundManager().playOneSoundEffect(((AudioAsset)Game.getGame().getAssetLibrary().getAudio(AssetLibrary.audioType.SOUND_EFFECT).get("death_scream")).sound, 1.0f);
                }
                burned = true;
                return co;
            }
            if (co is OozeObject)
            {
                if (health > 0 && myType.myType == ObjectFactory.TypeEnum.QuinHero)
                {
                    --health;
                    Game.getGame().getSoundManager().playOneSoundEffect(((AudioAsset)Game.getGame().getAssetLibrary().getAudio(AssetLibrary.audioType.SOUND_EFFECT).get("death_ooze")).sound, 1.0f);
                }
                melted = true;
                return co;
            }
            if (co is ElectricObject)
            {
                if (health > 0 && myType.myType == ObjectFactory.TypeEnum.QuinHero)
                {
                    --health;
                    Game.getGame().getSoundManager().playOneSoundEffect(((AudioAsset)Game.getGame().getAssetLibrary().getAudio(AssetLibrary.audioType.SOUND_EFFECT).get("death_zap")).sound, 1.0f);
                }
                electrocuted = true;
                return co;
            }
        }
        return null;
    }

    protected bool checkSquashed()
    {
        if (!squashed &&
            LevelBase.collidesWith(min + new Vector3(1, 1, 1), max - new Vector3(1, 1, 1), this).Count > 0)
        {
            squashed = true;   
        }
        return squashed;
    }

    protected void updateShadow()
    {
        //find the closest walkable object
        //find x/y placement of the shadow
        //make rect size proportional to height
        float shadowY = float.MaxValue;
        List<CollidableObject> floorCheck = LevelBase.collidesWith(
            new Vector3(min.X, min.Y + thisType.collisionBoxDimentions.Y, min.Z),
            new Vector3(max.X, max.Y + 1000, max.Z),
            this);
        foreach (CollidableObject c in floorCheck)
        {
            if (c is ViewableObject && ((ViewableObject)c).walkable && shadowY > c.collisionMin.Y)
            {
                shadowY = c.collisionMin.Y;
            }
        }
        float distanceFactor = (shadowY - origin.Y)/1000;
        Vector3 backLeft = new Vector3(origin.X-thisType.collisionBoxOriginOffset.X, shadowY, origin.Z+thisType.collisionBoxDimentions.Z);
        Vector3 frontRight = new Vector3(backLeft.X+thisType.collisionBoxDimentions.X, shadowY, origin.Z);
        Vector3 screenBackLeft = Camera.worldToScreen(backLeft);
        Vector3 screenFrontRight = Camera.worldToScreen(frontRight);
        float widthDiff = (screenFrontRight.X - screenBackLeft.X)*distanceFactor;
        float heightDiff = (screenFrontRight.Y - screenBackLeft.Y)*distanceFactor;
        shadowDest = new Rectangle(
            (int)(screenBackLeft.X + (widthDiff / 2)), 
            (int)(screenBackLeft.Y + (heightDiff / 2)), 
            (int)(screenFrontRight.X - screenBackLeft.X - widthDiff), 
            (int)(screenFrontRight.Y - screenBackLeft.Y-heightDiff));
    }

    protected void updateFalling()
    {
        bool falling = true;
        showShadow = true;
        List<CollidableObject> floorCheck = LevelBase.collidesWith(
            new Vector3(min.X + 1, min.Y, min.Z + 1),
            new Vector3(max.X - 1, max.Y, max.Z - 1),
            this);
        List<CollidableObject> walkables = new List<CollidableObject>();
        List<CollidableObject> nonDestructables = new List<CollidableObject>();
        foreach (CollidableObject co in floorCheck)
        {
            if (co is ViewableObject && ((ViewableObject)co).walkable)
            {
                if (currVelocity.Y == maxDnSpeed.Y)
                {
                    destroy(this);
                }
                falling = false;
                showShadow = false;
                currVelocity = new Vector3(0, 0, 0);
                return;
            }
        }
        if (falling)
        {
            if (currVelocity.Y < maxDnSpeed.Y)
            {
                currVelocity += gravity;
                if (currVelocity.Y > maxDnSpeed.Y)
                {
                    currVelocity = maxDnSpeed;
                }
            }
        }
        if (currVelocity.Y > 0)
        {
            floorCheck = LevelBase.collidesWith(
                max - new Vector3(thisType.collisionBoxDimentions.X - 1, 0, thisType.collisionBoxDimentions.Z - 1),
                max + new Vector3(-1, currVelocity.Y, -1),
                this);
            foreach (CollidableObject co in floorCheck)
            {
                if (co is ViewableObject && ((ViewableObject)co).walkable)
                {
                    walkables.Add(co);
                }
                else if(!(co is DestructableObject))
                {
                    nonDestructables.Add(co);
                }
            }
            foreach (CollidableObject c in walkables)
            {
                if (c.collisionMin.Y < max.Y + currVelocity.Y)
                {
                    currVelocity.Y = c.collisionMin.Y - max.Y;
                }
            }
            foreach(CollidableObject c in nonDestructables)
            {
                if (c.collisionMin.Y < max.Y + currVelocity.Y)
                {
                    destroy(this);
                    currVelocity = jumpSpeed + new Vector3(-2, 0, 0);
                    walkables.Clear();
                    break;
                }
            }
        }
        else if (currVelocity.Y < 0)
        {
            floorCheck = LevelBase.collidesWith(
                min + new Vector3(1, currVelocity.Y, 1),
                min + new Vector3(thisType.collisionBoxDimentions.X - 1, 0, thisType.collisionBoxDimentions.Z - 1),
                this);
            foreach (CollidableObject co in floorCheck)
            {
                if (co is ViewableObject && ((ViewableObject)co).walkable)
                {
                    walkables.Add(co);
                }
                else if (!(co is DestructableObject))
                {
                    nonDestructables.Add(co);
                }
            }
            foreach (CollidableObject c in walkables)
            {
                if (c.collisionMax.Y > min.Y + currVelocity.Y)
                {
                    currVelocity.Y = c.collisionMax.Y - min.Y;
                }
            }
            foreach (CollidableObject c in nonDestructables)
            {
                if (c.collisionMax.Y < min.Y + currVelocity.Y)
                {
                    destroy(this);
                    currVelocity = jumpSpeed + new Vector3(-2, 0, 0);
                    walkables.Clear();
                    break;
                }
            }
        }
        origin += currVelocity;
        max += currVelocity;
        min += currVelocity;
    }

    public override void draw()
    {
        base.draw();
        if (showShadow)
        {
            Game.getGame().getSpriteBatch().Draw(shadow, shadowDest, null, Color.White, 0, new Vector2(0, 0), SpriteEffects.None, screenCoord.Z + (thisType.depthOffset));
        }
    }

    public new class Type : ViewableObject.Type
    {
        //breakable world objects
        public static readonly DestructableObject.Type Boulder = new Type(
            ObjectFactory.TypeEnum.Boulder,//type
            new Vector3(102, 156, 0),//origin
            new Vector3(205, 156, 205),//collision
            new ArrayList {
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.DEBRIS).get("debris"), 
                    new Rectangle(0,0,205,156), 
                    0, 
                    new Vector2(102,156))},
            new ArrayList {//1398x215
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.PARTICLES).get("dustImpactSpriteSheet"), 
                    new Rectangle(0,0,349,107), 
                    0, 
                    new Vector2(174,100)),
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.PARTICLES).get("dustImpactSpriteSheet"), 
                    new Rectangle(349,0,349,107), 
                    0, 
                    new Vector2(174,100)),
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.PARTICLES).get("dustImpactSpriteSheet"), 
                    new Rectangle(698,0,349,107), 
                    0, 
                    new Vector2(174,100)),
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.PARTICLES).get("dustImpactSpriteSheet"), 
                    new Rectangle(1047,0,349,107), 
                    0, 
                    new Vector2(174,100)),
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.PARTICLES).get("dustImpactSpriteSheet"), 
                    new Rectangle(0,107,349,107), 
                    0, 
                    new Vector2(174,100)),
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.PARTICLES).get("dustImpactSpriteSheet"), 
                    new Rectangle(349,107,349,107), 
                    0, 
                    new Vector2(174,100))});//frameList
        public static readonly DestructableObject.Type WeakWall = new Type();
        public static readonly DestructableObject.Type Tanker = new Type();

        //animation info for the explosion animation
        //but each instance of ViewableObject 
        //needs its own Animation instance
        /** the frame information for the explosion animation */
        public ArrayList explodeFrames
        {
            get { return explosionFrames; }
        }
        protected ArrayList explosionFrames;
        /** the name for the explosion animation */
        public String explodeName
        {
            get { return explosionName; }
        }
        protected String explosionName;
        /** the framerate for the explosion animation */
        public int explodeFramerate
        {
            get { return explosionFramerate; }
        }
        protected int explosionFramerate;
        /** whether or not the explosion animation is loopable */
        public Boolean explodeLoopable
        {
            get { return explosionLoopable; }
        }
        protected Boolean explosionLoopable;

        /**
         * this is a dummy constructor.
         */
        protected Type() { }

        /**
         * Private constructor.
         * This private constructor ensures that only 
         * the pre-specified Type objects can be used 
         * and compared against.
         * @param origin
         * @param collision
         * @param basic
         * @param destruction
         */
        protected Type(ObjectFactory.TypeEnum type, Vector3 origin, Vector3 collision, Boolean walkable,
            ArrayList stasisFrames, String stasisName, int stasisFramerate, Boolean stasisLoopable,
            ArrayList explosionFrames, String explosionName, int explosionFramerate, Boolean explosionLoopable)
            : base(type, origin, collision,walkable, stasisFrames, stasisName, stasisFramerate, stasisLoopable)
        {
            init(explosionFrames, explosionName, explosionFramerate, explosionLoopable);
        }

        protected Type(ObjectFactory.TypeEnum type, Vector3 origin, Vector3 collision,
            ArrayList stasisFrames, ArrayList explosionFrames)
            : base(type, origin, collision, stasisFrames)
        {
            init(explosionFrames, "explosion", 6, false);
        }

        protected void init(ArrayList explosionFrames, String explosionName, int explosionFramerate, Boolean explosionLoopable)
        {
            this.explosionFrames = explosionFrames;
            this.explosionName = explosionName;
            this.explosionFramerate = explosionFramerate;
            this.explosionLoopable = explosionLoopable;
            //and make sure no frame has bigger max/min cull boxes than the collision box cull boxes
        }

        public static new DestructableObject.Type getType(ObjectFactory.TypeEnum type)
        {
            switch (type)
            {
                case ObjectFactory.TypeEnum.Boulder: return Boulder;
                case ObjectFactory.TypeEnum.WeakWall: return WeakWall;
                case ObjectFactory.TypeEnum.Tanker: return Tanker;
            }
            return null;
        }
    }
}
